package com.engine.salary.util.valid;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.engine.salary.exception.SalaryRunTimeException;
import com.engine.salary.util.SalaryI18nUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import weaver.general.Util;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 参数校验
 * <p>Copyright: Copyright (c) 2022</p>
 * <p>Company: 泛微软件</p>
 *
 * @author qiantao
 * @version 1.0
 **/
@Slf4j
public class ValidUtil {

    /**
     * 通过反射来获取javaBean上的注解信息，判断属性值信息，然后通过注解元数据
     * 来返回
     *
     * @param t
     */
    public static <T> void doValidator(T t, RuntimeTypeEnum... runtime) {
        Class<?> clazz = t.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            DataCheck rule = field.getDeclaredAnnotation(DataCheck.class);

            if (null == rule) {
                continue;
            }

            boolean needValid = false;
            RuntimeTypeEnum[] runtimeTypeEnums = rule.runtime();
            if (Arrays.stream(runtimeTypeEnums).anyMatch(r -> r == RuntimeTypeEnum.AUTO)) {
                needValid = true;
            } else {
                //传了运行类型，校验字段上运行类型和传入的是否有交集
                needValid = hasSameEM(runtimeTypeEnums, runtime);
            }

            if (!needValid) {
                continue;
            }

            //字段值
            Object value = getValue(t, field.getName());
            ValidTypeEnum type = rule.type();
            int labelId = rule.labelId();
            String message = rule.message();
            boolean require = rule.require();
            int max = rule.max();
            int min = rule.min();
            String pattern = rule.pattern();

            if (require) {
                if (!notNull(value)) {
                    throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(labelId, message));
                }
            }

            if (ValidTypeEnum.STRING == type) {
                //区间值
                if (value != null && max != -1) {
                    String s = String.valueOf(value);
                    if (s.length() > max) {
                        throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(labelId, message));
                    }
                }

                if (value != null && min != -1) {
                    String s = String.valueOf(value);
                    if (s.length() < min) {
                        throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(labelId, message));
                    }
                }
            }

            if (ValidTypeEnum.DATE == type) {
                if (value != null && StringUtils.isNotBlank(value.toString()) && isValidDate(value.toString(), pattern)) {
                    throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(labelId, message));
                }
            }


            if (ValidTypeEnum.NUMBER == type) {
                if (value instanceof Integer) {
                    int v = (Integer) value;
                    if (max != -1) {
                        if (v > max) {
                            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(labelId, message));
                        }
                    }
                    if (min != -1) {
                        if (v < min) {
                            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(labelId, message));
                        }
                    }
                }

            }


        }
    }

    private static boolean hasSameEM(RuntimeTypeEnum[] runtimeTypeEnums, RuntimeTypeEnum[] runtime) {
        boolean needValid = false;
        HashSet<RuntimeTypeEnum> set = new HashSet<>(Arrays.asList(runtimeTypeEnums));
        set.retainAll(Arrays.asList(runtime));
        if (set.size() > 0) {
            needValid = true;
        }
        return needValid;
    }

    private static <T> Object getValue(T t, String fieldName) {
        Object value = null;
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass());
            PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : props) {
                if (fieldName.equals(property.getName())) {
                    Method method = property.getReadMethod();
                    value = method.invoke(t, new Object[]{});
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("获取属性值失败", e);
        }
        return value;
    }

    private static <T> void setValue(T t, String fieldName, Object value) {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass());
            PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : props) {
                if (fieldName.equals(property.getName())) {
                    Method method = property.getWriteMethod();
                    method.invoke(t, value);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static boolean notNull(Object value) {
        if (null == value) {
            return false;
        }
        if (value instanceof String && isEmpty((String) value)) {
            return false;
        }
        if (value instanceof List && isEmpty((Collection<?>) value)) {
            return false;
        }
        return true;
    }

    private static boolean isEmpty(String str) {
        return null == str || str.isEmpty();
    }

    private static boolean isEmpty(Collection<?> list) {
        return null == list || list.isEmpty();
    }


    /**
     * 校验日期
     */
    private static boolean isValidDate(String str, String pattern) {
        boolean convertSuccess = true;
        if (str != null && str.length() == pattern.length()) {
            // 指定日期格式为四位年/两位月份/两位日期，注意yyyy/MM/dd区分大小写；
            SimpleDateFormat format = new SimpleDateFormat(pattern);
            try {
                // 设置lenient为false.
                // 否则SimpleDateFormat会比较宽松地验证日期，比如2007/02/29会被接受，并转换成2007/03/01
                format.setLenient(false);
                Date dateStr = format.parse(str);
            } catch (Exception e) {
                // 如果throw java.text.ParseException或者NullPointerException，就说明格式不对
                convertSuccess = false;
            }
        } else {
            convertSuccess = false;
        }
        return convertSuccess;
    }

    /**
     * 处理参数值，trim，xss
     *
     * @param t
     * @param <T>
     */
    public static <T> void modify(T t) {
        Class<?> clazz = t.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            Modify rule = field.getDeclaredAnnotation(Modify.class);

            if (null == rule) {
                continue;
            }

            ModifyTypeEnum[] modifyTypeEnums = rule.modifyType();
            ValueTypeEnum valueTypeEnum = rule.valueType();
            for (int i = 0; i < modifyTypeEnums.length; i++) {
                Object value = getValue(t, field.getName());
                if (valueTypeEnum == ValueTypeEnum.STRING) {
                    String result = value.toString();

                    if (modifyTypeEnums[i] == ModifyTypeEnum.TRIM) {
                        result = result.trim();
                    }

                    if (modifyTypeEnums[i] == ModifyTypeEnum.RESTORE_SQL) {
                        result = result.replace("ｓｅｌｅｃｔ", "select")
                                .replace("ＳＥＬＥＣＴ", "SELECT")
                                .replace("ｊｏｉｎ", "join")
                                .replace("ＪＯＩＮ", "JOIN")
                                .replace("ａｎｄ", "and")
                                .replace("ＡＮＤ", "AND")
                                .replace("ｏｒ", "or")
                                .replace("ＯＲ", "OR")
                                .replace("ｉｎ", "in")
                                .replace("ＩＮ", "IN")
                                .replace("ｌｉｋｅ", "like")
                                .replace("ＬＩＫＥ", "LIKE")
                                .replace("ｅｘｉｓｔｓ", "exists")
                                .replace("ＥＸＩＳＴＳ", "EXISTS")
                                .replace("ｂｅｔｗｅｅｎ", "between")
                                .replace("ＢＥＴＷＥＥＮ", "BETWEEN")
                                .replace("ｕｎｉｏｎ", "union")
                                .replace("ＵＮＩＯＮ", "UNION")
                                .replace("ｓｕｂｓｔｒ", "substr")
                                .replace("ＳＵＢＳＴＲ", "SUBSTR");
                    }
                    setValue(t, field.getName(), result);
                } else if (valueTypeEnum == ValueTypeEnum.OBJECT) {
                    modify(value);
                } else if (valueTypeEnum == ValueTypeEnum.ARRAY) {
                    Collection list = (Collection) value;
                    list.forEach(l -> modify(l));
                }
            }
        }
    }

    /**
     * 比较两个对象，如果值不相等，则抛出异常
     * <p>
     * true代表相等，false代表不相等
     *
     * @param o
     * @param n
     * @param <T>
     */
    public static <T> boolean compare(T o, T n) {
        Class<?> clazz = o.getClass();
        Field[] fields = clazz.getDeclaredFields();
        boolean result = true;
        for (Field field : fields) {
            Compare rule = field.getDeclaredAnnotation(Compare.class);
            if (null == rule) {
                continue;
            }
            boolean reportError = rule.reportError();
            String message = rule.message();

            ValueTypeEnum valueTypeEnum = rule.type();
            Object oValue = getValue(o, field.getName());
            Object nValue = getValue(n, field.getName());
            if (valueTypeEnum == ValueTypeEnum.STRING) {
                result = result && StrUtil.equals(Util.null2String(oValue), Util.null2String(nValue));
                if (!result && reportError) {
                    throw new RuntimeException(message);
                }
            } else if (valueTypeEnum == ValueTypeEnum.DATE) {
                result = result && DateUtil.compare((Date) o, (Date) n) == 0;
                if (!result && reportError) {
                    throw new RuntimeException(message);
                }
            } else if (valueTypeEnum == ValueTypeEnum.OBJECT) {
                result = result && compare(oValue, nValue);
            }
        }
        return result;
    }


}
